/**
 * @file   Shape.h
 * @author ubuntu <dadi@ubuntu>
 * @date   Wed Apr 13 14:47:58 2022
 * 
 * @brief  A demo for factory.
 * 
 * 
 */


#ifndef __DADI_SHAPE__
#define __DADI_SHAPE__

#include <iostream>
#include <string>
#include <map>

class Shape;
class Rectangle;
class Triangle;

/** 
 * 2D
 * 
 */
class Shape
{
protected:
    double width;
    double height;
    
public:
    Shape(){};
    void reset(double a, double b){width = a; height = b;};
    /** 
     * Compute the area of the shape.
     * 
     */
    virtual void area() = 0;
};

class Rectangle : public Shape
{
public:
    Rectangle(){width = 0; height = 0;};
    Rectangle(double a, double b);
    void area() override;
};

Rectangle::Rectangle(double a, double b)
{
    width = a;
    height = b;
};

void Rectangle::area()
{
    double area;
    area = width*height;
    std::cout << "The area of the rectangle is " << area << "." << std::endl;
};

class Triangle : public Shape
{
public:
    Triangle(){width = 0; height = 0;};
    Triangle(double a, double b);
    void area() override;
};

Triangle::Triangle(double a, double b)
{
    width = a;
    height = b;
};

void Triangle::area()
{
    double area;
    area = width*height/2.0;
    std::cout << "The area of the triangle is " << area << "." << std::endl;
};

class ShapeFactory
{
public:
    typedef Shape* (*ShapeCreator)();

public:
    /** 
     * Returns 'true' if sucessfully register.
     * 
     * @param shapeId 
     * @param createFn 
     * 
     * @return 
     */
    bool RegisterShape(const std::string &shapeId, ShapeCreator createFn);
    /** 
     * Returns 'true' if sucessfully unregister.
     * 
     * @param shapeId 
     * 
     * @return 
     */
    bool UnregisterShape(const std::string &shapeId);
    Shape* CreateShape(const std::string &shapeId) const;
    static ShapeFactory* Instance()
	{
	    if(!pInstance)
		pInstance = new ShapeFactory;
	    return pInstance;
	};

private:
    typedef std::map<std::string, ShapeCreator> CallbackMap;
    CallbackMap callbacks_;
    static ShapeFactory* pInstance;
    ShapeFactory(){};
};

bool ShapeFactory::RegisterShape(const std::string &shapeId, ShapeCreator createFn)
{
    return callbacks_.insert(CallbackMap::value_type(shapeId, createFn)).second;
};

bool ShapeFactory::UnregisterShape(const std::string &shapeId)
{
    return callbacks_.erase(shapeId) == 1;
};

Shape* ShapeFactory::CreateShape(const std::string &shapeId) const
{
    auto i = callbacks_.find(shapeId);
    if (i == callbacks_.cend())
    {
	throw std::runtime_error("Unknown Shape ID");
    }
    return (i->second)();
};

ShapeFactory* ShapeFactory::pInstance = NULL;

namespace
{
    Shape* CreateRectangle()
    {
	return new Rectangle;
    }
    Shape* CreateTriangle()
    {
	return new Triangle;
    }
    const bool Rectangle_registered = ShapeFactory::Instance()->RegisterShape("Rectangle", CreateRectangle);
    const bool Triangle_registered = ShapeFactory::Instance()->RegisterShape("Triangle", CreateTriangle);
}

#else
//Do nothing.
#endif
